/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    BeanVisual.java
 *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.beans;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;

/**
 * BeanVisual encapsulates icons and label for a given bean. Has methods
 * to load icons, set label text and toggle between static and animated
 * versions of a bean's icon.
 *
 * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
 * @version $Revision: 7154 $
 * @since 1.0
 * @see JPanel
 * @see Serializable
 */
public class BeanVisual
  extends JPanel {

  /** for serialization */
  private static final long serialVersionUID = -6677473561687129614L;

  public static final String ICON_PATH="weka/gui/beans/icons/";

  public static final int NORTH_CONNECTOR = 0;
  public static final int SOUTH_CONNECTOR = 1;
  public static final int EAST_CONNECTOR = 2;
  public static final int WEST_CONNECTOR = 3;

  /**
   * Holds name (including path) of the static icon
   */
  protected String m_iconPath;

  /**
   * Holds name (including path) of the animated icon
   */
  protected String m_animatedIconPath;

  /**
   * ImageIcons for the icons. Is transient because for some reason
   * animated gifs cease to be animated after restoring from serialization.
   * Icons are re-loaded from source after deserialization
   */
  protected transient ImageIcon m_icon;
  protected transient ImageIcon m_animatedIcon;

  /**
   * Name for the bean
   */
  protected String m_visualName;

  protected JLabel m_visualLabel;
  
  /**
   * Container for the icon
   */
  //  protected IconHolder m_visualHolder;

  //  protected JLabel m_textLabel;
  private boolean m_stationary = true;

  private PropertyChangeSupport m_pcs = new PropertyChangeSupport(this);
  
  private boolean m_displayConnectors = false;
  private Color m_connectorColor = Color.blue;

  /**
   * Constructor
   *
   * @param visualName name for the bean
   * @param iconPath path to the icon file
   * @param animatedIconPath path to the animated icon file
   */
  public BeanVisual(String visualName, String iconPath, 
		    String animatedIconPath) {

    loadIcons(iconPath, animatedIconPath);
    m_visualName = visualName;
    //    m_textLabel = new JLabel(m_visualName, JLabel.CENTER);
    m_visualLabel = new JLabel(m_icon);

    setLayout(new BorderLayout());
   
    //    m_visualHolder = new IconHolder(m_visualLabel);
    
    add(m_visualLabel, BorderLayout.CENTER);
    Dimension d = m_visualLabel.getPreferredSize();
    //      this.setSize((int)d.getWidth()+50, (int)d.getHeight()+50);
    Dimension d2 = new Dimension((int)d.getWidth() + 10, 
				 (int)d.getHeight() + 10);
    setMinimumSize(d2);
    setPreferredSize(d2);
    setMaximumSize(d2);
  }

  /**
   * Reduce this BeanVisual's icon size by the given factor
   *
   * @param factor the factor by which to reduce the icon size by
   */
  public void scale(int factor) {
    if (m_icon != null) {
      removeAll();
      Image pic = m_icon.getImage();
      int width = m_icon.getIconWidth();
      int height = m_icon.getIconHeight();
      int reduction = width / factor;
      width -= reduction;
      height -= reduction;
      pic = pic.getScaledInstance(width, height, Image.SCALE_SMOOTH);
      m_icon = new ImageIcon(pic);
      m_visualLabel = new JLabel(m_icon);
      add(m_visualLabel, BorderLayout.CENTER);
      Dimension d = m_visualLabel.getPreferredSize();
      //      this.setSize((int)d.getWidth()+50, (int)d.getHeight()+50);
      Dimension d2 = new Dimension((int)d.getWidth() + 10, 
				   (int)d.getHeight() + 10);
      setMinimumSize(d2);
      setPreferredSize(d2);
      setMaximumSize(d2);   
    }
  }
  
  public Image scale(double percent) {
    if (m_icon != null) {
      Image pic = m_icon.getImage();
      double width = m_icon.getIconWidth();
      double height = m_icon.getIconHeight();
      
      width *= percent;
      height *= percent;
      
      pic = pic.getScaledInstance((int)width, (int)height, Image.SCALE_SMOOTH);
      
      return pic;
    }
    
    return null;
  }

  /**
   * Loads static and animated versions of a beans icons. These are
   * assumed to be defined in the system resource location (i.e. in the
   * CLASSPATH). If the named icons do not exist, no changes to the
   * visual appearance is made. Since default icons for generic
   * types of beans (eg. DataSource, Classifier etc)
   * are assumed to exist, it allows developers to add custom icons for
   * for specific instantiations of these beans 
   * (eg. J48, DiscretizeFilter etc) at their leisure.
   *
   * @param iconPath path to
   * @param animatedIconPath a <code>String</code> value
   */
  public boolean loadIcons(String iconPath, String animatedIconPath) {
    boolean success = true;
    //    java.net.URL imageURL = ClassLoader.getSystemResource(iconPath);
    java.net.URL imageURL = this.getClass().getClassLoader().getResource(iconPath);
    if (imageURL == null) {
      //      System.err.println("Warning: unable to load "+iconPath);
    } else {
      Image pic = Toolkit.getDefaultToolkit().
	getImage(imageURL);

      m_icon = new ImageIcon(pic);
      if (m_visualLabel != null) {
	m_visualLabel.setIcon(m_icon);
      }
    }
    
    //    imageURL = ClassLoader.getSystemResource(animatedIconPath);
    imageURL = this.getClass().getClassLoader().getResource(animatedIconPath);
    if (imageURL == null) {
      //      System.err.println("Warning: unable to load "+animatedIconPath);
      success = false;
    } else {
      Image pic2 = Toolkit.getDefaultToolkit().
	getImage(imageURL);
      m_animatedIcon = new ImageIcon(pic2);
    }
    m_iconPath = iconPath;
    m_animatedIconPath = animatedIconPath;
    return success;
  }

  /**
   * Set the label for the visual. Informs any property change listeners
   *
   * @param text the label
   */
  public void setText(String text) {
    m_visualName = text;
    //    m_textLabel.setText(m_visualName);
    m_pcs.firePropertyChange("label",null,null);
  }

  /**
   * Get the visual's label
   *
   * @return a <code>String</code> value
   */
  public String getText() {
    return m_visualName;
  }

  /**
   * Set the static version of the icon
   *
   */
  public void setStatic() {
    setDisplayConnectors(false);
    //m_visualLabel.setIcon(m_icon);
  }

  /**
   * Set the animated version of the icon
   *
   */
  public void setAnimated() {
    setDisplayConnectors(true);
    //m_visualLabel.setIcon(m_animatedIcon);
  }

  /**
   * Returns the coordinates of the closest "connector" point to the
   * supplied point. Coordinates are in the parent containers coordinate
   * space.
   *
   * @param pt the reference point
   * @return the closest connector point
   */
  public Point getClosestConnectorPoint(Point pt) {
    int sourceX = getParent().getX();
    int sourceY = getParent().getY();
    int sourceWidth = getWidth();
    int sourceHeight = getHeight();
    int sourceMidX = sourceX + (sourceWidth / 2);
    int sourceMidY = sourceY + (sourceHeight / 2);
    int x = (int)pt.getX();
    int y = (int)pt.getY();
    
    Point closest = new Point();
    int cx = (Math.abs(x - sourceMidX) < Math.abs(y - sourceMidY)) ? 
      sourceMidX :
      ((x < sourceMidX) ? sourceX : sourceX + sourceWidth);
    int cy = (Math.abs(y - sourceMidY) < Math.abs(x - sourceMidX)) ? 
      sourceMidY :
      ((y < sourceMidY) ? sourceY : sourceY + sourceHeight) ;
    closest.setLocation(cx, cy);
    return closest;
  }

  /**
   * Returns the coordinates of the connector point given a compass point
   *
   * @param compassPoint a compass point
   * @return a <code>Point</code> value
   */
  public Point getConnectorPoint(int compassPoint) {
    int sourceX = getParent().getX();
    int sourceY = getParent().getY();
    int sourceWidth = getWidth();
    int sourceHeight = getHeight();
    int sourceMidX = sourceX + (sourceWidth / 2);
    int sourceMidY = sourceY + (sourceHeight / 2);

    switch (compassPoint) {
    case NORTH_CONNECTOR : return new Point(sourceMidX, sourceY);
    case SOUTH_CONNECTOR : return new Point(sourceMidX, sourceY+sourceHeight);
    case WEST_CONNECTOR :  return new Point(sourceX, sourceMidY);
    case EAST_CONNECTOR :  return new Point(sourceX+sourceWidth, sourceMidY);
    default : System.err.println("Unrecognised connectorPoint (BeanVisual)");
    }
    return new Point(sourceX, sourceY);
  }

  /**
   * Returns the static icon
   *
   * @return an <code>ImageIcon</code> value
   */
  public ImageIcon getStaticIcon() {
    return m_icon;
  }

  /**
   * Returns the animated icon
   *
   * @return an <code>ImageIcon</code> value
   */
  public ImageIcon getAnimatedIcon() {
    return m_animatedIcon;
  }

  /**
   * returns the path for the icon
   * 
   * @return the path for the icon
   */
  public String getIconPath() {
    return m_iconPath;
  }

  /**
   * returns the path for the animated icon
   * 
   * @return the path for the animated icon
   */
  public String getAnimatedIconPath() {
    return m_animatedIconPath;
  }

  /**
   * Turn on/off the connector points
   *
   * @param dc a <code>boolean</code> value
   */
  public void setDisplayConnectors(boolean dc) {
    //    m_visualHolder.setDisplayConnectors(dc);
    m_displayConnectors = dc;
    m_connectorColor = Color.blue;
    repaint();
  }

  /**
   * Turn on/off the connector points
   *
   * @param dc a <code>boolean</code> value
   * @param c the Color to use
   */
  public void setDisplayConnectors(boolean dc,
                                   Color c) {
    setDisplayConnectors(dc);
    m_connectorColor = c;
  }

  /**
   * Add a listener for property change events
   *
   * @param pcl a <code>PropertyChangeListener</code> value
   */
  public void addPropertyChangeListener(PropertyChangeListener pcl) {
    m_pcs.addPropertyChangeListener(pcl);
  }

  /**
   * Remove a property change listener
   *
   * @param pcl a <code>PropertyChangeListener</code> value
   */
  public void removePropertyChangeListener(PropertyChangeListener pcl) {
    m_pcs.removePropertyChangeListener(pcl);
  }

  public void paintComponent(Graphics gx) {
    ((Graphics2D)gx).setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
        RenderingHints.VALUE_ANTIALIAS_ON);
    
    super.paintComponent(gx);
    if (m_displayConnectors) {
      gx.setColor(m_connectorColor);
      
      int midx = (int)(this.getWidth() / 2.0);
      int midy = (int)(this.getHeight() / 2.0);
      gx.fillOval(midx-2, 0, 5, 5);
      gx.fillOval(midx-2, this.getHeight()-5, 5, 5);
      gx.fillOval(0, midy-2, 5, 5);
      gx.fillOval(this.getWidth()-5, midy-2, 5, 5);
    }
  }

  /**
   * Overides default read object in order to reload icons.
   * This is necessary because for some strange reason animated
   * gifs stop being animated after being serialized/deserialized.
   *
   * @param ois an <code>ObjectInputStream</code> value
   * @exception IOException if an error occurs
   * @exception ClassNotFoundException if an error occurs
   */
  private void readObject(ObjectInputStream ois) 
    throws IOException, ClassNotFoundException {
    try {
      ois.defaultReadObject();
      remove(m_visualLabel);
      m_visualLabel = new JLabel(m_icon);
      loadIcons(m_iconPath, m_animatedIconPath);
      add(m_visualLabel, BorderLayout.CENTER);
      Dimension d = m_visualLabel.getPreferredSize();
      Dimension d2 = new Dimension((int)d.getWidth() + 10, 
				   (int)d.getHeight() + 10);
      setMinimumSize(d2);
      setPreferredSize(d2);
      setMaximumSize(d2);   
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
